import sys
import cv2
import numpy as np
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout,
                             QHBoxLayout, QLabel, QPushButton, QFileDialog, QSlider,
                             QComboBox, QMessageBox, QGroupBox, QGridLayout, QAction,
                             QToolBar, QSplitter, QDockWidget, QTextEdit, QProgressBar,
                             QSpinBox, QDoubleSpinBox, QDialog, QLineEdit, QFormLayout,
                             QColorDialog, QInputDialog, QGraphicsScene, QGraphicsView,
                             QGraphicsPixmapItem, QScrollArea)
from PyQt5.QtGui import QPixmap, QImage, QIcon, QFont, QPainter, QPen, QColor
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal, QPoint, QRect, QSize
from PyQt5 import QtCore


# 人脸数据库类
class FaceDatabase:
    def __init__(self):
        self.faces = {}  # 格式: {name: (face_image, descriptor)}

    def add_face(self, name, face_image, descriptor=None):
        """添加人脸到数据库"""
        self.faces[name] = (face_image, descriptor)

    def remove_face(self, name):
        """从数据库移除人脸"""
        if name in self.faces:
            del self.faces[name]
            return True
        return False

    def get_all_faces(self):
        """获取所有注册的人脸名称"""
        return list(self.faces.keys())

    def recognize_face(self, face_descriptor, threshold=0.6):
        """
        人脸匹配识别
        返回: (匹配名称, 相似度)
        """
        if not self.faces or face_descriptor is None:
            return None, 0

        best_match = None
        highest_similarity = 0

        # 简单的欧氏距离匹配
        for name, (_, db_descriptor) in self.faces.items():
            if db_descriptor is not None:
                # 计算欧氏距离
                distance = np.linalg.norm(np.array(face_descriptor) - np.array(db_descriptor))
                # 转换为相似度 (距离越小，相似度越高)
                similarity = 1 / (1 + distance)

                if similarity > highest_similarity and similarity >= threshold:
                    highest_similarity = similarity
                    best_match = name

        return best_match, highest_similarity


# 视频处理线程类
class VideoThread(QThread):
    change_pixmap_signal = pyqtSignal(np.ndarray)
    finished_signal = pyqtSignal()
    progress_signal = pyqtSignal(int)  # 视频进度信号

    def __init__(self, video_path=None):
        super().__init__()
        self.video_path = video_path
        self._run_flag = True
        self.processing_function = lambda frame: frame  # 默认不处理
        self._pause_flag = False  # 暂停标志
        self._position = 0  # 当前帧位置
        self.total_frames = 0  # 总帧数

    def run(self):
        # 打开摄像头或视频文件
        if self.video_path:
            cap = cv2.VideoCapture(self.video_path)
            self.total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
        else:
            cap = cv2.VideoCapture(0)
            self.total_frames = 0

        # 设置初始位置
        if self._position > 0 and self.video_path:
            cap.set(cv2.CAP_PROP_POS_FRAMES, self._position)

        while self._run_flag:
            if not self._pause_flag:
                ret, cv_img = cap.read()
                if ret:
                    self._position = int(cap.get(cv2.CAP_PROP_POS_FRAMES))
                    # 计算进度百分比
                    if self.total_frames > 0:
                        progress = int((self._position / self.total_frames) * 100)
                        self.progress_signal.emit(progress)

                    # 应用图像处理函数
                    processed_img = self.processing_function(cv_img)
                    self.change_pixmap_signal.emit(processed_img)
                else:
                    # 视频结束或出错
                    self.finished_signal.emit()
                    break
            # 控制帧率
            self.msleep(30)

        # 释放资源
        cap.release()
        self.finished_signal.emit()

    def stop(self):
        """停止线程"""
        self._run_flag = False
        self.wait()

    def pause(self, pause=True):
        """暂停或继续视频播放"""
        self._pause_flag = pause

    def set_position(self, position):
        """设置视频播放位置（百分比）"""
        if self.video_path and self.total_frames > 0:
            self._position = int((position / 100) * self.total_frames)


# 主窗口类
class ImageVideoProcessor(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.current_image = None
        self.original_image = None  # 保存原始图像用于重置
        self.current_video_thread = None
        self.is_video_playing = False
        self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
        self.face_db = FaceDatabase()  # 人脸数据库
        self.crop_start_point = None
        self.crop_end_point = None
        self.is_cropping = False

    def init_ui(self):
        # 设置窗口
        self.setWindowTitle('图像处理与视频分析平台')
        self.setGeometry(100, 100, 1200, 800)

        # 创建菜单栏
        self.create_menu_bar()

        # 创建工具栏
        self.create_tool_bar()

        # 创建中央部件
        self.central_widget = QTabWidget()
        self.setCentralWidget(self.central_widget)

        # 添加图像和视频处理选项卡
        self.image_tab = QWidget()
        self.video_tab = QWidget()
        self.central_widget.addTab(self.image_tab, '图像处理')
        self.central_widget.addTab(self.video_tab, '视频处理')

        # 设置图像选项卡
        self.setup_image_tab()

        # 设置视频选项卡
        self.setup_video_tab()

        # 创建状态栏
        self.statusBar().showMessage('就绪')

    def create_menu_bar(self):
        # 创建菜单栏
        menubar = self.menuBar()

        # 文件菜单
        file_menu = menubar.addMenu('文件')

        # 打开图像动作
        open_image_action = QAction('打开图像', self)
        open_image_action.setShortcut('Ctrl+I')
        open_image_action.triggered.connect(self.open_image)
        file_menu.addAction(open_image_action)

        # 打开视频动作
        open_video_action = QAction('打开视频', self)
        open_video_action.setShortcut('Ctrl+V')
        open_video_action.triggered.connect(self.open_video)
        file_menu.addAction(open_video_action)

        # 保存图像动作
        save_image_action = QAction('保存图像', self)
        save_image_action.setShortcut('Ctrl+S')
        save_image_action.triggered.connect(self.save_image)
        file_menu.addAction(save_image_action)

        # 保存视频帧动作
        save_frame_action = QAction('保存当前视频帧', self)
        save_frame_action.setShortcut('Ctrl+F')
        save_frame_action.triggered.connect(self.save_video_frame)
        file_menu.addAction(save_frame_action)

        file_menu.addSeparator()

        # 退出动作
        exit_action = QAction('退出', self)
        exit_action.setShortcut('Ctrl+Q')
        exit_action.triggered.connect(self.close)
        file_menu.addAction(exit_action)

        # 编辑菜单
        edit_menu = menubar.addMenu('编辑')

        # 撤销动作
        undo_action = QAction('撤销', self)
        undo_action.setShortcut('Ctrl+Z')
        undo_action.triggered.connect(self.undo_operation)
        edit_menu.addAction(undo_action)

        # 复制动作
        copy_action = QAction('复制', self)
        copy_action.setShortcut('Ctrl+C')
        copy_action.triggered.connect(self.copy_image)
        edit_menu.addAction(copy_action)

        # 粘贴动作
        paste_action = QAction('粘贴', self)
        paste_action.setShortcut('Ctrl+V')
        paste_action.triggered.connect(self.paste_image)
        edit_menu.addAction(paste_action)

        # 视图菜单
        view_menu = menubar.addMenu('视图')

        # 放大动作
        zoom_in_action = QAction('放大', self)
        zoom_in_action.setShortcut('Ctrl++')
        zoom_in_action.triggered.connect(lambda: self.zoom_image(1.2))
        view_menu.addAction(zoom_in_action)

        # 缩小动作
        zoom_out_action = QAction('缩小', self)
        zoom_out_action.setShortcut('Ctrl+-')
        zoom_out_action.triggered.connect(lambda: self.zoom_image(0.8))
        view_menu.addAction(zoom_out_action)

        # 重置缩放动作
        reset_zoom_action = QAction('重置缩放', self)
        reset_zoom_action.setShortcut('Ctrl+0')
        reset_zoom_action.triggered.connect(self.reset_zoom)
        view_menu.addAction(reset_zoom_action)

        # 帮助菜单
        help_menu = menubar.addMenu('帮助')

        # 关于动作
        about_action = QAction('关于', self)
        about_action.triggered.connect(self.show_about)
        help_menu.addAction(about_action)

        # 使用教程动作
        tutorial_action = QAction('使用教程', self)
        tutorial_action.triggered.connect(self.show_tutorial)
        help_menu.addAction(tutorial_action)

    def create_tool_bar(self):
        # 创建工具栏
        toolbar = QToolBar('工具栏')
        self.addToolBar(toolbar)

        # 添加图像工具按钮
        open_image_btn = QPushButton('打开图像')
        open_image_btn.clicked.connect(self.open_image)
        toolbar.addWidget(open_image_btn)

        save_image_btn = QPushButton('保存图像')
        save_image_btn.clicked.connect(self.save_image)
        toolbar.addWidget(save_image_btn)

        toolbar.addSeparator()

        # 添加视频工具按钮
        open_video_btn = QPushButton('打开视频')
        open_video_btn.clicked.connect(self.open_video)
        toolbar.addWidget(open_video_btn)

        self.start_video_btn = QPushButton('开始播放')
        self.start_video_btn.clicked.connect(self.start_video)
        toolbar.addWidget(self.start_video_btn)

        self.stop_video_btn = QPushButton('停止播放')
        self.stop_video_btn.clicked.connect(self.stop_video)
        self.stop_video_btn.setEnabled(False)
        toolbar.addWidget(self.stop_video_btn)

        # 添加人脸注册按钮
        self.register_face_btn = QPushButton('注册人脸')
        self.register_face_btn.clicked.connect(self.register_face)
        toolbar.addWidget(self.register_face_btn)

    def setup_image_tab(self):
        # 创建主布局
        main_layout = QHBoxLayout(self.image_tab)

        # 创建左侧控制面板
        control_panel = QWidget()
        control_layout = QVBoxLayout(control_panel)
        control_panel.setMaximumWidth(300)

        # 图像加载组
        load_group = QGroupBox('图像加载')
        load_layout = QVBoxLayout(load_group)

        load_btn = QPushButton('加载图像')
        load_btn.clicked.connect(self.open_image)
        load_layout.addWidget(load_btn)

        reset_btn = QPushButton('重置图像')
        reset_btn.clicked.connect(self.reset_image)
        load_layout.addWidget(reset_btn)

        control_layout.addWidget(load_group)

        # 图像处理组
        process_group = QGroupBox('图像处理')
        process_layout = QGridLayout(process_group)

        # 灰度转换按钮
        gray_btn = QPushButton('灰度转换')
        gray_btn.clicked.connect(lambda: self.process_image('gray'))
        process_layout.addWidget(gray_btn, 0, 0)

        # 边缘检测按钮
        edge_btn = QPushButton('边缘检测')
        edge_btn.clicked.connect(lambda: self.process_image('edge'))
        process_layout.addWidget(edge_btn, 0, 1)

        # 模糊处理按钮
        blur_btn = QPushButton('模糊处理')
        blur_btn.clicked.connect(lambda: self.process_image('blur'))
        process_layout.addWidget(blur_btn, 1, 0)

        # 锐化处理按钮
        sharpen_btn = QPushButton('锐化处理')
        sharpen_btn.clicked.connect(lambda: self.process_image('sharpen'))
        process_layout.addWidget(sharpen_btn, 1, 1)

        # 二值化按钮
        thresh_btn = QPushButton('二值化')
        thresh_layout = QHBoxLayout()
        self.thresh_value = QSlider(Qt.Horizontal)
        self.thresh_value.setRange(0, 255)
        self.thresh_value.setValue(127)
        thresh_layout.addWidget(self.thresh_value)
        thresh_btn.clicked.connect(lambda: self.process_image('threshold'))
        thresh_layout.addWidget(thresh_btn)
        process_layout.addLayout(thresh_layout, 2, 0, 1, 2)

        control_layout.addWidget(process_group)

        # 图像编辑组
        edit_group = QGroupBox('图像编辑')
        edit_layout = QGridLayout(edit_group)

        # 旋转按钮
        rotate_btn = QPushButton('旋转')
        rotate_btn.clicked.connect(self.rotate_image)
        edit_layout.addWidget(rotate_btn, 0, 0)

        # 缩放按钮
        scale_btn = QPushButton('缩放')
        scale_btn.clicked.connect(self.scale_image)
        edit_layout.addWidget(scale_btn, 0, 1)

        # 裁剪按钮
        crop_btn = QPushButton('裁剪')
        crop_btn.clicked.connect(self.start_crop)
        edit_layout.addWidget(crop_btn, 1, 0)

        # 应用裁剪按钮
        apply_crop_btn = QPushButton('应用裁剪')
        apply_crop_btn.clicked.connect(self.apply_crop)
        edit_layout.addWidget(apply_crop_btn, 1, 1)

        # 亮度/对比度调整
        brightness_btn = QPushButton('亮度/对比度')
        brightness_btn.clicked.connect(self.adjust_brightness_contrast)
        edit_layout.addWidget(brightness_btn, 2, 0, 1, 2)

        control_layout.addWidget(edit_group)

        # 形状识别组
        shape_group = QGroupBox('形状识别')
        shape_layout = QVBoxLayout(shape_group)

        # 人脸检测按钮
        face_btn = QPushButton('人脸检测')
        face_btn.clicked.connect(lambda: self.process_image('face'))
        shape_layout.addWidget(face_btn)

        # 圆形检测按钮
        circle_btn = QPushButton('圆形检测')
        circle_btn.clicked.connect(lambda: self.process_image('circle'))
        shape_layout.addWidget(circle_btn)

        # 矩形检测按钮
        rectangle_btn = QPushButton('矩形检测')
        rectangle_btn.clicked.connect(lambda: self.process_image('rectangle'))
        shape_layout.addWidget(rectangle_btn)

        # 三角形检测按钮
        triangle_btn = QPushButton('三角形检测')
        triangle_btn.clicked.connect(lambda: self.process_image('triangle'))
        shape_layout.addWidget(triangle_btn)

        # 多边形检测按钮
        polygon_btn = QPushButton('多边形检测')
        polygon_btn.clicked.connect(lambda: self.process_image('polygon'))
        shape_layout.addWidget(polygon_btn)

        control_layout.addWidget(shape_group)

        # 图像拼接组
        stitch_group = QGroupBox('图像拼接')
        stitch_layout = QVBoxLayout(stitch_group)

        stitch_btn = QPushButton('添加到拼接')
        stitch_btn.clicked.connect(self.add_to_stitch)
        stitch_layout.addWidget(stitch_btn)

        stitch_process_btn = QPushButton('执行拼接')
        stitch_process_btn.clicked.connect(self.stitch_images)
        stitch_layout.addWidget(stitch_process_btn)

        control_layout.addWidget(stitch_group)

        # 添加到主布局
        main_layout.addWidget(control_panel)

        # 创建右侧图像显示区域
        self.image_display = QLabel('请加载图像')
        self.image_display.setAlignment(Qt.AlignCenter)
        self.image_display.setMinimumSize(400, 400)
        self.image_display.setMouseTracking(True)  # 启用鼠标追踪
        self.image_display.mousePressEvent = self.image_mouse_press
        self.image_display.mouseMoveEvent = self.image_mouse_move
        self.image_display.mouseReleaseEvent = self.image_mouse_release

        # 将图像显示区域放入滚动区域
        self.image_scroll_area = QScrollArea()
        self.image_scroll_area.setWidgetResizable(True)
        self.image_scroll_area.setWidget(self.image_display)

        main_layout.addWidget(self.image_scroll_area, 1)

        # 初始化图像拼接列表
        self.images_to_stitch = []
        self.image_history = []  # 图像操作历史

    def setup_video_tab(self):
        # 创建主布局
        main_layout = QVBoxLayout(self.video_tab)

        # 创建视频控制区域
        control_layout = QHBoxLayout()

        # 打开视频按钮
        open_video_btn = QPushButton('打开视频')
        open_video_btn.clicked.connect(self.open_video)
        control_layout.addWidget(open_video_btn)

        # 开始播放按钮
        self.start_video_btn = QPushButton('开始播放')
        self.start_video_btn.clicked.connect(self.start_video)
        control_layout.addWidget(self.start_video_btn)

        # 暂停播放按钮
        self.pause_video_btn = QPushButton('暂停')
        self.pause_video_btn.clicked.connect(self.pause_video)
        self.pause_video_btn.setEnabled(False)
        control_layout.addWidget(self.pause_video_btn)

        # 停止播放按钮
        self.stop_video_btn = QPushButton('停止播放')
        self.stop_video_btn.clicked.connect(self.stop_video)
        self.stop_video_btn.setEnabled(False)
        control_layout.addWidget(self.stop_video_btn)

        # 选择摄像头按钮
        self.camera_btn = QPushButton('使用摄像头')
        self.camera_btn.clicked.connect(self.use_camera)
        control_layout.addWidget(self.camera_btn)

        # 快退按钮
        self.rewind_btn = QPushButton('快退')
        self.rewind_btn.clicked.connect(lambda: self.seek_video(-10))
        self.rewind_btn.setEnabled(False)
        control_layout.addWidget(self.rewind_btn)

        # 快进按钮
        self.forward_btn = QPushButton('快进')
        self.forward_btn.clicked.connect(lambda: self.seek_video(10))
        self.forward_btn.setEnabled(False)
        control_layout.addWidget(self.forward_btn)

        main_layout.addLayout(control_layout)

        # 创建视频处理选择
        process_layout = QHBoxLayout()

        # 添加视频处理选择
        self.video_process_combo = QComboBox()
        self.video_process_combo.addItems(['无处理', '灰度', '边缘检测', '人脸检测', '人脸识别'])
        self.video_process_combo.currentIndexChanged.connect(self.change_video_processing)
        process_layout.addWidget(self.video_process_combo)

        # 人脸匹配结果显示
        self.match_result_label = QLabel('匹配结果:')
        process_layout.addWidget(self.match_result_label)

        main_layout.addLayout(process_layout)

        # 创建视频显示区域
        self.video_display = QLabel('请打开视频或选择摄像头')
        self.video_display.setAlignment(Qt.AlignCenter)
        self.video_display.setMinimumSize(640, 480)

        main_layout.addWidget(self.video_display, 1)

        # 创建进度条
        self.video_progress = QProgressBar()
        self.video_progress.setRange(0, 100)
        self.video_progress.setValue(0)
        main_layout.addWidget(self.video_progress)

    def open_image(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getOpenFileName(
            self, "打开图像", "", "图像文件 (*.png *.jpg *.jpeg *.bmp *.gif);;所有文件 (*)", options=options)

        if file_name:
            self.current_image = cv2.imread(file_name)
            self.original_image = self.current_image.copy()  # 保存原始图像
            self.display_image(self.current_image)
            self.statusBar().showMessage(f'加载图像: {file_name}')
            self.image_history = []  # 清空历史
            self.is_cropping = False
            self.crop_start_point = None
            self.crop_end_point = None

    def display_image(self, img):
        if img is not None:
            # 保存当前图像到历史
            self.save_to_history()

            # 转换为RGB
            if len(img.shape) == 3:
                rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            else:
                rgb_img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB)

            # 如果正在裁剪，显示裁剪框
            if self.is_cropping and self.crop_start_point and self.crop_end_point:
                painter = QPainter(rgb_img)
                painter.setPen(QPen(QColor(255, 0, 0), 2, Qt.DashLine))
                painter.drawRect(QRect(self.crop_start_point, self.crop_end_point))
                painter.end()

            # 创建QImage
            h, w, ch = rgb_img.shape
            bytes_per_line = ch * w
            q_img = QImage(rgb_img.data, w, h, bytes_per_line, QImage.Format_RGB888)

            # 显示图像
            self.image_display.setPixmap(QPixmap.fromImage(q_img).scaled(
                self.image_display.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))

    def process_image(self, operation):
        if self.current_image is None:
            QMessageBox.warning(self, '警告', '请先加载图像')
            return

        processed_img = None

        if operation == 'gray':
            processed_img = cv2.cvtColor(self.current_image, cv2.COLOR_BGR2GRAY)

        elif operation == 'edge':
            gray = cv2.cvtColor(self.current_image, cv2.COLOR_BGR2GRAY)
            processed_img = cv2.Canny(gray, 100, 200)

        elif operation == 'blur':
            processed_img = cv2.GaussianBlur(self.current_image, (15, 15), 0)

        elif operation == 'sharpen':
            kernel = np.array([[-1, -1, -1], [-1, 9, -1], [-1, -1, -1]])
            processed_img = cv2.filter2D(self.current_image, -1, kernel)

        elif operation == 'threshold':
            gray = cv2.cvtColor(self.current_image, cv2.COLOR_BGR2GRAY)
            thresh_value = self.thresh_value.value()
            _, processed_img = cv2.threshold(gray, thresh_value, 255, cv2.THRESH_BINARY)

        elif operation == 'face':
            processed_img = self.current_image.copy()
            gray = cv2.cvtColor(processed_img, cv2.COLOR_BGR2GRAY)
            faces = self.face_cascade.detectMultiScale(gray, 1.1, 4)

            for (x, y, w, h) in faces:
                cv2.rectangle(processed_img, (x, y), (x + w, y + h), (255, 0, 0), 2)

            self.statusBar().showMessage(f'检测到 {len(faces)} 个人脸')

        elif operation == 'circle':
            processed_img = self.current_image.copy()
            gray = cv2.cvtColor(processed_img, cv2.COLOR_BGR2GRAY)
            gray = cv2.medianBlur(gray, 5)

            circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20,
                                       param1=50, param2=30, minRadius=0, maxRadius=0)

            if circles is not None:
                circles = np.uint16(np.around(circles))
                for i in circles[0, :]:
                    # 绘制外圆
                    cv2.circle(processed_img, (i[0], i[1]), i[2], (0, 255, 0), 2)
                    # 绘制圆心
                    cv2.circle(processed_img, (i[0], i[1]), 2, (0, 0, 255), 3)

                self.statusBar().showMessage(f'检测到 {len(circles[0])} 个圆形')

        elif operation == 'rectangle':
            processed_img = self.current_image.copy()
            gray = cv2.cvtColor(processed_img, cv2.COLOR_BGR2GRAY)
            _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

            contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

            rectangles = []
            for contour in contours:
                epsilon = 0.02 * cv2.arcLength(contour, True)
                approx = cv2.approxPolyDP(contour, epsilon, True)

                if len(approx) == 4:
                    rectangles.append(approx)
                    cv2.drawContours(processed_img, [approx], 0, (0, 0, 255), 2)

            self.statusBar().showMessage(f'检测到 {len(rectangles)} 个矩形')

        elif operation == 'triangle':
            processed_img = self.current_image.copy()
            gray = cv2.cvtColor(processed_img, cv2.COLOR_BGR2GRAY)
            _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

            contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

            triangles = []
            for contour in contours:
                epsilon = 0.02 * cv2.arcLength(contour, True)
                approx = cv2.approxPolyDP(contour, epsilon, True)

                if len(approx) == 3:
                    triangles.append(approx)
                    cv2.drawContours(processed_img, [approx], 0, (0, 255, 0), 2)

            self.statusBar().showMessage(f'检测到 {len(triangles)} 个三角形')

        elif operation == 'polygon':
            processed_img = self.current_image.copy()
            gray = cv2.cvtColor(processed_img, cv2.COLOR_BGR2GRAY)
            _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)

            contours, _ = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

            polygons = []
            for contour in contours:
                epsilon = 0.02 * cv2.arcLength(contour, True)
                approx = cv2.approxPolyDP(contour, epsilon, True)

                if len(approx) > 4:
                    polygons.append(approx)
                    cv2.drawContours(processed_img, [approx], 0, (255, 0, 255), 2)

            self.statusBar().showMessage(f'检测到 {len(polygons)} 个多边形')

        if processed_img is not None:
            self.current_image = processed_img
            self.display_image(self.current_image)

    def add_to_stitch(self):
        if self.current_image is not None:
            self.images_to_stitch.append(self.current_image)
            self.statusBar().showMessage(f'已添加图像到拼接列表，当前列表大小: {len(self.images_to_stitch)}')
        else:
            QMessageBox.warning(self, '警告', '请先加载图像')

    def stitch_images(self):
        if len(self.images_to_stitch) < 2:
            QMessageBox.warning(self, '警告', '至少需要两张图像进行拼接')
            return

        stitcher = cv2.Stitcher_create()
        status, stitched_img = stitcher.stitch(self.images_to_stitch)

        if status == cv2.Stitcher_OK:
            self.current_image = stitched_img
            self.display_image(stitched_img)
            self.statusBar().showMessage('图像拼接成功')
        else:
            QMessageBox.warning(self, '拼接失败', f'拼接失败，错误代码: {status}')
            self.statusBar().showMessage('图像拼接失败')

    def save_image(self):
        if self.current_image is None:
            QMessageBox.warning(self, '警告', '请先加载或处理图像')
            return

        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getSaveFileName(
            self, "保存图像", "", "PNG文件 (*.png);;JPEG文件 (*.jpg);;所有文件 (*)", options=options)

        if file_name:
            cv2.imwrite(file_name, self.current_image)
            self.statusBar().showMessage(f'图像已保存到: {file_name}')

    def open_video(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getOpenFileName(
            self, "打开视频", "", "视频文件 (*.mp4 *.avi *.mov *.mkv);;所有文件 (*)", options=options)

        if file_name:
            self.stop_video()
            self.current_video_path = file_name
            self.statusBar().showMessage(f'加载视频: {file_name}')
            self.rewind_btn.setEnabled(True)
            self.forward_btn.setEnabled(True)

    def start_video(self):
        if hasattr(self, 'current_video_path') or self.current_video_thread is None:
            if not hasattr(self, 'current_video_path'):
                self.use_camera()
                return

            self.stop_video()

            # 创建视频线程
            self.current_video_thread = VideoThread(self.current_video_path)
            self.current_video_thread.change_pixmap_signal.connect(self.update_video_frame)
            self.current_video_thread.finished_signal.connect(self.video_finished)
            self.current_video_thread.progress_signal.connect(self.update_video_progress)

            # 设置视频处理函数
            self.change_video_processing()

            # 启动线程
            self.current_video_thread.start()
            self.is_video_playing = True
            self.start_video_btn.setEnabled(False)
            self.pause_video_btn.setEnabled(True)
            self.stop_video_btn.setEnabled(True)
            self.statusBar().showMessage('视频播放中...')
        else:
            QMessageBox.warning(self, '警告', '请先打开视频')

    def stop_video(self):
        if self.current_video_thread is not None and self.is_video_playing:
            self.current_video_thread.stop()
            self.is_video_playing = False
            self.start_video_btn.setEnabled(True)
            self.pause_video_btn.setEnabled(False)
            self.stop_video_btn.setEnabled(False)
            self.statusBar().showMessage('视频已停止')

    def pause_video(self):
        if self.current_video_thread is not None and self.is_video_playing:
            if self.current_video_thread._pause_flag:
                self.current_video_thread.pause(False)
                self.pause_video_btn.setText('暂停')
                self.statusBar().showMessage('视频播放中...')
            else:
                self.current_video_thread.pause(True)
                self.pause_video_btn.setText('继续')
                self.statusBar().showMessage('视频已暂停')

    def use_camera(self):
        self.stop_video()

        # 创建视频线程（使用摄像头）
        self.current_video_thread = VideoThread()
        self.current_video_thread.change_pixmap_signal.connect(self.update_video_frame)
        self.current_video_thread.finished_signal.connect(self.video_finished)

        # 设置视频处理函数
        self.change_video_processing()

        # 启动线程
        self.current_video_thread.start()
        self.is_video_playing = True
        self.start_video_btn.setEnabled(False)
        self.pause_video_btn.setEnabled(True)
        self.stop_video_btn.setEnabled(True)
        self.statusBar().showMessage('正在使用摄像头...')
        self.rewind_btn.setEnabled(False)  # 摄像头模式禁用快进快退
        self.forward_btn.setEnabled(False)

    def update_video_frame(self, cv_img):
        # 转换为RGB
        rgb_img = cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)

        # 创建QImage
        h, w, ch = rgb_img.shape
        bytes_per_line = ch * w
        q_img = QImage(rgb_img.data, w, h, bytes_per_line, QImage.Format_RGB888)

        # 显示图像
        self.video_display.setPixmap(QPixmap.fromImage(q_img).scaled(
            self.video_display.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))

    def update_video_progress(self, progress):
        """更新视频进度条"""
        self.video_progress.setValue(progress)

    def change_video_processing(self):
        if self.current_video_thread is not None:
            process_type = self.video_process_combo.currentText()

            if process_type == '无处理':
                self.current_video_thread.processing_function = lambda frame: frame
            elif process_type == '灰度':
                self.current_video_thread.processing_function = lambda frame: cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            elif process_type == '边缘检测':
                self.current_video_thread.processing_function = lambda frame: cv2.Canny(frame, 100, 200)
            elif process_type == '人脸检测':
                def detect_faces(frame):
                    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
                    faces = self.face_cascade.detectMultiScale(gray, 1.1, 4)

                    for (x, y, w, h) in faces:
                        cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)

                    return frame

                self.current_video_thread.processing_function = detect_faces
            elif process_type == '人脸识别':
                def recognize_faces(frame):
                    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
                    faces = self.face_cascade.detectMultiScale(gray, 1.1, 4)

                    for (x, y, w, h) in faces:
                        # 提取人脸区域
                        face_roi = frame[y:y + h, x:x + w]

                        # 简单特征提取（实际应用中应使用更复杂的特征提取方法）
                        face_descriptor = np.mean(face_roi, axis=(0, 1))

                        # 人脸匹配
                        match_name, similarity = self.face_db.recognize_face(face_descriptor)

                        # 绘制人脸框和匹配结果
                        color = (0, 255, 0) if match_name else (0, 0, 255)
                        cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2)

                        # 添加匹配信息
                        if match_name:
                            text = f"{match_name}: {similarity:.2f}"
                            self.match_result_label.setText(f'匹配结果: {match_name} ({similarity:.2f})')
                        else:
                            text = "Unknown"
                            self.match_result_label.setText('匹配结果: 未识别')

                        cv2.putText(frame, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, color, 2)

                    return frame

                self.current_video_thread.processing_function = recognize_faces

    def video_finished(self):
        """视频播放完成或停止时调用"""
        self.is_video_playing = False
        self.start_video_btn.setEnabled(True)
        self.pause_video_btn.setEnabled(False)
        self.stop_video_btn.setEnabled(False)
        self.statusBar().showMessage('视频播放完成')

    def seek_video(self, delta):
        """快进或快退视频"""
        if self.current_video_thread and self.current_video_thread.total_frames > 0:
            current_pos = int((self.current_video_thread._position / self.current_video_thread.total_frames) * 100)
            new_pos = max(0, min(100, current_pos + delta))
            self.current_video_thread.set_position(new_pos)
            self.statusBar().showMessage(f'视频位置: {new_pos}%')

    def save_video_frame(self):
        """保存当前视频帧"""
        if self.current_video_thread and not self.current_video_thread._pause_flag:
            self.pause_video()  # 暂停以获取稳定帧

            # 转换当前显示的视频帧为OpenCV格式
            pixmap = self.video_display.pixmap()
            if not pixmap.isNull():
                image = pixmap.toImage()
                ptr = image.bits()
                ptr.setsize(image.byteCount())
                arr = np.array(ptr).reshape(image.height(), image.width(), 4)  # 按RGBA格式
                cv_img = cv2.cvtColor(arr, cv2.COLOR_RGBA2BGR)  # 转换为BGR

                # 保存图像
                options = QFileDialog.Options()
                file_name, _ = QFileDialog.getSaveFileName(
                    self, "保存视频帧", "", "PNG文件 (*.png);;JPEG文件 (*.jpg);;所有文件 (*)", options=options)

                if file_name:
                    cv2.imwrite(file_name, cv_img)
                    self.statusBar().showMessage(f'视频帧已保存到: {file_name}')
            else:
                QMessageBox.warning(self, '警告', '没有可保存的视频帧')

    def rotate_image(self):
        """旋转图像"""
        if self.current_image is None:
            QMessageBox.warning(self, '警告', '请先加载图像')
            return

        angle, ok = QInputDialog.getDouble(self, '旋转图像', '输入旋转角度:', 0, -360, 360, 1)
        if ok:
            rows, cols = self.current_image.shape[:2]
            M = cv2.getRotationMatrix2D((cols / 2, rows / 2), angle, 1)
            self.current_image = cv2.warpAffine(self.current_image, M, (cols, rows))
            self.display_image(self.current_image)

    def scale_image(self):
        """缩放图像"""
        if self.current_image is None:
            QMessageBox.warning(self, '警告', '请先加载图像')
            return

        factor, ok = QInputDialog.getDouble(self, '缩放图像', '输入缩放因子:', 1.0, 0.1, 10.0, 2)
        if ok:
            rows, cols = self.current_image.shape[:2]
            self.current_image = cv2.resize(self.current_image, (int(cols * factor), int(rows * factor)))
            self.display_image(self.current_image)

    def start_crop(self):
        """开始裁剪图像"""
        if self.current_image is None:
            QMessageBox.warning(self, '警告', '请先加载图像')
            return

        self.is_cropping = True
        self.crop_start_point = None
        self.crop_end_point = None
        self.display_image(self.current_image)
        self.statusBar().showMessage('请在图像上拖动鼠标选择裁剪区域')

    def image_mouse_press(self, event):
        """鼠标按下事件处理"""
        if self.is_cropping and event.button() == Qt.LeftButton:
            self.crop_start_point = event.pos()
            self.crop_end_point = None
            self.display_image(self.current_image)

    def image_mouse_move(self, event):
        """鼠标移动事件处理"""
        if self.is_cropping and self.crop_start_point and event.buttons() & Qt.LeftButton:
            self.crop_end_point = event.pos()
            self.display_image(self.current_image)

    def image_mouse_release(self, event):
        """鼠标释放事件处理"""
        if self.is_cropping and self.crop_start_point and event.button() == Qt.LeftButton:
            self.crop_end_point = event.pos()
            self.display_image(self.current_image)

    def apply_crop(self):
        """应用裁剪"""
        if self.is_cropping and self.crop_start_point and self.crop_end_point:
            # 获取实际图像上的坐标
            pixmap = self.image_display.pixmap()
            if not pixmap.isNull():
                # 计算图像在QLabel中的位置和缩放比例
                img_size = pixmap.size()
                label_size = self.image_display.size()
                scale = min(label_size.width() / img_size.width(), label_size.height() / img_size.height())

                # 计算图像在QLabel中的偏移
                offset_x = (label_size.width() - img_size.width() * scale) / 2
                offset_y = (label_size.height() - img_size.height() * scale) / 2

                # 转换坐标
                x1 = max(0, int((self.crop_start_point.x() - offset_x) / scale))
                y1 = max(0, int((self.crop_start_point.y() - offset_y) / scale))
                x2 = max(0, int((self.crop_end_point.x() - offset_x) / scale))
                y2 = max(0, int((self.crop_end_point.y() - offset_y) / scale))

                # 确保x1 < x2, y1 < y2
                x1, x2 = min(x1, x2), max(x1, x2)
                y1, y2 = min(y1, y2), max(y1, y2)

                # 确保坐标在图像范围内
                height, width = self.current_image.shape[:2]
                x1 = min(x1, width - 1)
                y1 = min(y1, height - 1)
                x2 = min(x2, width)
                y2 = min(y2, height)

                # 裁剪图像
                if x2 > x1 and y2 > y1:
                    self.current_image = self.current_image[y1:y2, x1:x2]
                    self.is_cropping = False
                    self.crop_start_point = None
                    self.crop_end_point = None
                    self.display_image(self.current_image)
                    self.statusBar().showMessage('图像已裁剪')
                else:
                    QMessageBox.warning(self, '警告', '裁剪区域无效')
            else:
                QMessageBox.warning(self, '警告', '没有可裁剪的图像')
        else:
            QMessageBox.warning(self, '警告', '请先选择裁剪区域')

    def adjust_brightness_contrast(self):
        """调整亮度和对比度"""
        if self.current_image is None:
            QMessageBox.warning(self, '警告', '请先加载图像')
            return

        # 创建对话框
        dialog = QDialog(self)
        dialog.setWindowTitle('调整亮度和对比度')
        layout = QFormLayout(dialog)

        # 亮度滑块
        brightness_label = QLabel('亮度:')
        brightness_slider = QSlider(Qt.Horizontal)
        brightness_slider.setRange(-100, 100)
        brightness_slider.setValue(0)
        layout.addRow(brightness_label, brightness_slider)

        # 对比度滑块
        contrast_label = QLabel('对比度:')
        contrast_slider = QSlider(Qt.Horizontal)
        contrast_slider.setRange(-100, 100)
        contrast_slider.setValue(0)
        layout.addRow(contrast_label, contrast_slider)

        # 确定和取消按钮
        button_layout = QHBoxLayout()
        ok_button = QPushButton('确定')
        cancel_button = QPushButton('取消')
        button_layout.addWidget(ok_button)
        button_layout.addWidget(cancel_button)
        layout.addRow(button_layout)

        # 连接信号和槽
        ok_button.clicked.connect(dialog.accept)
        cancel_button.clicked.connect(dialog.reject)

        # 显示对话框
        if dialog.exec_():
            brightness = brightness_slider.value()
            contrast = contrast_slider.value()

            # 应用亮度和对比度调整
            if brightness != 0 or contrast != 0:
                # 调整公式: output = contrast * input + brightness
                alpha = 1.0 + contrast / 100.0
                beta = brightness

                self.current_image = cv2.convertScaleAbs(self.current_image, alpha=alpha, beta=beta)
                self.display_image(self.current_image)

    def register_face(self):
        """注册人脸"""
        if self.current_image is None and (
                not hasattr(self, 'current_video_thread') or not self.current_video_thread.isRunning()):
            QMessageBox.warning(self, '警告', '请先加载图像或启动视频')
            return

        # 如果是视频模式，获取当前帧
        if hasattr(self, 'current_video_thread') and self.current_video_thread.isRunning():
            # 暂停视频以获取稳定帧
            was_playing = self.is_video_playing
            if was_playing:
                self.pause_video()

            # 转换当前显示的视频帧为OpenCV格式
            pixmap = self.video_display.pixmap()
            if not pixmap.isNull():
                image = pixmap.toImage()
                ptr = image.bits()
                ptr.setsize(image.byteCount())
                arr = np.array(ptr).reshape(image.height(), image.width(), 4)  # 按RGBA格式
                frame = cv2.cvtColor(arr, cv2.COLOR_RGBA2BGR)  # 转换为BGR
            else:
                QMessageBox.warning(self, '警告', '无法获取当前视频帧')
                if was_playing:
                    self.pause_video()  # 恢复播放
                return

            # 恢复播放状态
            if was_playing:
                self.pause_video()
        else:
            frame = self.current_image

        # 检测人脸
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faces = self.face_cascade.detectMultiScale(gray, 1.1, 4)

        if len(faces) == 0:
            QMessageBox.warning(self, '警告', '未检测到人脸')
            return

        if len(faces) > 1:
            reply = QMessageBox.question(self, '多人脸', '检测到多个人脸，是否注册第一个人脸？',
                                         QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
            if reply == QMessageBox.No:
                return

        # 获取第一个人脸
        x, y, w, h = faces[0]
        face_roi = frame[y:y + h, x:x + w]

        # 获取人脸名称
        name, ok = QInputDialog.getText(self, '注册人脸', '输入人脸名称:')
        if ok and name:
            # 简单特征提取（实际应用中应使用更复杂的特征提取方法）
            face_descriptor = np.mean(face_roi, axis=(0, 1))

            # 添加到人脸数据库
            self.face_db.add_face(name, face_roi, face_descriptor)
            self.statusBar().showMessage(f'已注册人脸: {name}')
            QMessageBox.information(self, '成功', f'已成功注册人脸: {name}')

    def save_to_history(self):
        """保存当前图像到历史记录"""
        if self.current_image is not None:
            # 限制历史记录大小
            if len(self.image_history) >= 10:
                self.image_history.pop(0)
            self.image_history.append(self.current_image.copy())

    def undo_operation(self):
        """撤销操作"""
        if len(self.image_history) > 0:
            self.current_image = self.image_history.pop()
            self.display_image(self.current_image)
            self.statusBar().showMessage('已撤销操作')
        else:
            QMessageBox.warning(self, '警告', '没有可撤销的操作')

    def zoom_image(self, factor):
        """缩放图像显示"""
        current_pixmap = self.image_display.pixmap()
        if current_pixmap and not current_pixmap.isNull():
            new_size = current_pixmap.size() * factor
            self.image_display.setPixmap(current_pixmap.scaled(
                new_size, Qt.KeepAspectRatio, Qt.SmoothTransformation))

    def reset_zoom(self):
        """重置图像缩放"""
        current_pixmap = self.image_display.pixmap()
        if current_pixmap and not current_pixmap.isNull():
            self.image_display.setPixmap(current_pixmap.scaled(
                self.image_display.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation))

    def reset_image(self):
        """重置图像为原始状态"""
        if self.original_image is not None:
            self.current_image = self.original_image.copy()
            self.image_history = []  # 清空历史
            self.display_image(self.current_image)
            self.statusBar().showMessage('图像已重置')

    def copy_image(self):
        """复制图像到剪贴板"""
        if self.current_image is not None:
            # 转换为RGB
            if len(self.current_image.shape) == 3:
                rgb_img = cv2.cvtColor(self.current_image, cv2.COLOR_BGR2RGB)
            else:
                rgb_img = cv2.cvtColor(self.current_image, cv2.COLOR_GRAY2RGB)

            # 创建QImage
            h, w, ch = rgb_img.shape
            bytes_per_line = ch * w
            q_img = QImage(rgb_img.data, w, h, bytes_per_line, QImage.Format_RGB888)

            # 复制到剪贴板
            clipboard = QApplication.clipboard()
            clipboard.setImage(q_img)
            self.statusBar().showMessage('图像已复制到剪贴板')

    def paste_image(self):
        """从剪贴板粘贴图像"""
        clipboard = QApplication.clipboard()
        q_img = clipboard.image()

        if not q_img.isNull():
            # 转换为OpenCV格式
            ptr = q_img.bits()
            ptr.setsize(q_img.byteCount())
            arr = np.array(ptr).reshape(q_img.height(), q_img.width(), 4)  # 按RGBA格式
            self.current_image = cv2.cvtColor(arr, cv2.COLOR_RGBA2BGR)  # 转换为BGR

            # 保存原始图像
            self.original_image = self.current_image.copy()

            # 显示图像
            self.image_history = []  # 清空历史
            self.display_image(self.current_image)
            self.statusBar().showMessage('已从剪贴板粘贴图像')
        else:
            QMessageBox.warning(self, '警告', '剪贴板中没有图像')

    def show_about(self):
        """显示关于对话框"""
        QMessageBox.about(self, '关于',
                          '图像处理与视频分析平台\n\n'
                          '版本: 1.0\n'
                          '作者: Doubao\n\n'
                          '这是一个基于Python和OpenCV的图像处理与视频分析平台，'
                          '支持图像加载、编辑、形状识别和视频处理等功能。')

    def show_tutorial(self):
        """显示使用教程"""
        QMessageBox.information(self, '使用教程',
                                '图像处理与视频分析平台使用教程\n\n'
                                '1. 图像处理:\n'
                                '   - 点击"打开图像"加载本地图像\n'
                                '   - 使用左侧面板进行图像处理操作\n'
                                '   - 支持灰度转换、边缘检测、模糊处理等多种功能\n\n'
                                '2. 视频处理:\n'
                                '   - 点击"打开视频"加载本地视频或"使用摄像头"开启摄像头\n'
                                '   - 使用播放控制按钮控制视频播放\n'
                                '   - 选择视频处理类型进行实时处理\n\n'
                                '3. 人脸检测与识别:\n'
                                '   - 先使用"注册人脸"功能注册人脸\n'
                                '   - 在视频处理中选择"人脸识别"进行实时识别\n\n'
                                '4. 图像拼接:\n'
                                '   - 加载多张图像并依次点击"添加到拼接"\n'
                                '   - 点击"执行拼接"生成全景图')

    def closeEvent(self, event):
        """窗口关闭事件"""
        if self.current_video_thread and self.current_video_thread.isRunning():
            self.current_video_thread.stop()
        event.accept()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = ImageVideoProcessor()
    window.show()
    sys.exit(app.exec_())